
#include <iostream>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <string>
#include <vector>

#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
namespace po = boost::program_options;

#include <boost/algorithm/string.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;

#include "bocat/common/md5.hpp"
#include "bocat/common/url.hpp"
#include "bocat/common/logging.hpp"
#include "aucab.hpp"

using namespace std;


#define	N_ROUND	100000

static bocat::common::Logger*   logger_p = NULL;

#define INFO(ARGS...)   logger_p->info(__LINE__,__FILE__,ARGS)
#define ERROR(ARGS...)  logger_p->error(__LINE__,__FILE__,ARGS)

float
getTimeDuration( ptime time_begin, ptime time_end )
{
	time_period	tp( time_begin, time_end );
	time_duration	td = tp.length();
	long	time_last_ms = td.total_milliseconds();
	float	time_last_s = time_last_ms / 1000.0f;

	return time_last_s;
}



void	test_001( const std::string& ini_file, const std::string& ini_section, const std::string& url_file )
{
	sakcab::Aucab	ac( ini_file.c_str(), ini_section.c_str() );

	std::ifstream	ifs( url_file.c_str() );
	if ( !ifs ) {
		ERROR( "fail to open url list file %s", url_file.c_str() );
		return;
	}

	int32_t	value_i = 1;
	std::string value( (char *)&value_i, sizeof(int32_t) );

	// test PUT
	{
		INFO( "testing PUT ..." );
		ptime	time_start = microsec_clock::local_time();
		ptime	time_start_R = time_start;
		u_int32_t	count_down_R = N_ROUND;
		u_int32_t	count_round = 0;

		bocat::common::MD5	m;

		int		times = 0;
		long	bytes_total = 0;
		ifs.clear();
		ifs.seekg(0, ios::beg);
		while( !ifs.eof() ) {
			std::string	line;
			ifs >> line;

			boost::trim( line );
			if ( line.size() < 7 ) {
				continue;
			}

            std::string url_reversed;
            try  {
                bocat::common::URL  url( line );
                url_reversed = url.getReversedURL();
                boost::trim( url_reversed );
            }   
            catch (std::exception& e) {
                ERROR( "Exception : %s", e.what() );
                ERROR( "BAD LINE : %s", line.c_str() );
                //std::cerr << "Exception : " << e.what() << std::endl;
                //std::cerr << "BAD LINE : " << line << std::endl;
                continue;
            }   

            const std::string&  key = url_reversed;

			m.clear();
			m.update( key.c_str(), key.size() );
			string	kpin((const char* )(m.digest()), m.size());

			//int	cid = i % max_cid;
			int	cid = 0;
			std::string& data = value;

			ac.insert( cid, kpin, key, data );
			bytes_total += data.size();

			++times;
			--count_down_R;
			if (0 == count_down_R) {
				ptime   time_stop_R = microsec_clock::local_time();
				float   time_last_s = getTimeDuration( time_start_R, time_stop_R );
				float   tps = N_ROUND / time_last_s;
                INFO("  R-PUT %u*%u records last %.3f seconds, tps=%.2f", ++count_round, N_ROUND, time_last_s, tps);

				time_start_R = time_stop_R;
				count_down_R = N_ROUND;
			}
		}

		ptime	time_stop = microsec_clock::local_time();
		float	time_last_s = getTimeDuration( time_start, time_stop );
		float	tps = times / time_last_s;
		float	bw = bytes_total / time_last_s / 1048576.0;
        INFO("    PUT %u records last %.3f seconds, tps=%.2f, bw=%.2f MB/s", times, time_last_s, tps, bw);
	}

	// test SLIM
	{
		INFO( "testing SLIM ..." );
		ptime	time_start = microsec_clock::local_time();

		int rc = ac.slim();
		if (rc < 0) {
			ERROR( "fail to slim() (rc=%d)", rc );
			return;
		}

		ptime	time_stop = microsec_clock::local_time();
		float	time_last_s = getTimeDuration( time_start, time_stop );
		INFO( "    SLIM %d records last %.2f seconds", rc, time_last_s );
	}

	// test GET
	{
		INFO( "testing GET ..." );
		ptime	time_start = microsec_clock::local_time();
		ptime	time_start_R = time_start;
		u_int32_t	count_down_R = N_ROUND;
		u_int32_t	count_round = 0;

		bocat::common::MD5	m;

		int		times = 0;
		long	bytes_total = 0;
		ifs.clear();
		ifs.seekg(0, ios::beg);
		while( !ifs.eof() ) {
			std::string	line;
			ifs >> line;

			boost::trim( line );
			if ( line.size() < 7 ) {
				continue;
			}

            std::string url_reversed;
            try  {
                bocat::common::URL  url( line );
                url_reversed = url.getReversedURL();
                boost::trim( url_reversed );
            }   
            catch (std::exception& e) {
                ERROR( "Exception : %s", e.what() );
                ERROR( "BAD LINE : %s", line.c_str() );
                //std::cerr << "Exception : " << e.what() << std::endl;
                //std::cerr << "BAD LINE : " << line << std::endl;
                continue;
            }   

            const std::string&  key = url_reversed;

			m.clear();
			m.update( key.c_str(), key.size() );
			string	kpin((const char* )(m.digest()), m.size());

			//int	cid = i % max_cid;
			int	cid = 0;
			std::string	data;

			int rc = ac.find( cid, kpin, key, data );
			if (data.size() < 1) {
				ERROR( "fail to get KEY: %s (rc=%d)", key.c_str(), rc );
				return;
			}

			bytes_total += data.size();

			++times;
			--count_down_R;
			if (0 == count_down_R) {
				ptime   time_stop_R = microsec_clock::local_time();
				float   time_last_s = getTimeDuration( time_start_R, time_stop_R );
				float   tps = N_ROUND / time_last_s;
                INFO("  R-GET %u*%u records last %.3f seconds, tps=%.2f", ++count_round, N_ROUND, time_last_s, tps);

				time_start_R = time_stop_R;
				count_down_R = N_ROUND;
			}
		}

		ptime	time_stop = microsec_clock::local_time();
		float	time_last_s = getTimeDuration( time_start, time_stop );
		float	tps = times / time_last_s;
		float	bw = bytes_total / time_last_s / 1048576.0;
        INFO("  GET %u records last %.3f seconds, tps=%.2f, bw=%.2f MB/s", times, time_last_s, tps, bw);
	}


	// test ERASE
	{
		INFO( "testing ERASE ..." );
		ptime	time_start = microsec_clock::local_time();
		ptime	time_start_R = time_start;
		u_int32_t	count_down_R = N_ROUND;
		u_int32_t	count_round = 0;

		bocat::common::MD5	m;

		int		times = 0;
		ifs.clear();
		ifs.seekg(0, ios::beg);
		while( !ifs.eof() ) {
			std::string	line;
			ifs >> line;

			boost::trim( line );
			if ( line.size() < 7 ) {
				continue;
			}

            std::string url_reversed;
            try  {
                bocat::common::URL  url( line );
                url_reversed = url.getReversedURL();
                boost::trim( url_reversed );
            }   
            catch (std::exception& e) {
                ERROR( "Exception : %s", e.what() );
                ERROR( "BAD LINE : %s", line.c_str() );
                //std::cerr << "Exception : " << e.what() << std::endl;
                //std::cerr << "BAD LINE : " << line << std::endl;
                continue;
            }   

            const std::string&  key = url_reversed;

			m.clear();
			m.update( key.c_str(), key.size() );
			string	kpin((const char* )(m.digest()), m.size());

			//int	cid = i % max_cid;
			int	cid = 0;

			ac.erase( cid, kpin, key );

			++times;
			--count_down_R;
			if (0 == count_down_R) {
				ptime   time_stop_R = microsec_clock::local_time();
				float   time_last_s = getTimeDuration( time_start_R, time_stop_R );
				float   tps = N_ROUND / time_last_s;
                INFO("  R-ERASE %u*%u records last %.3f seconds, tps=%.2f", ++count_round, N_ROUND, time_last_s, tps);

				time_start_R = time_stop_R;
				count_down_R = N_ROUND;
			}
		}

		ptime	time_stop = microsec_clock::local_time();
		float	time_last_s = getTimeDuration( time_start, time_stop );
		float	tps = times / time_last_s;
        INFO("  ERASE %u records last %.3f seconds, tps=%.2f", times, time_last_s, tps);
	}

	if (ifs.is_open()) {
		ifs.close();
	}
}


int	main(int argc, char* argv[])
{
	std::string	ini_file;
	std::string	ini_section;
	//int 	times = 0;
	//int 	data_size = 0;
	//int		max_cid = 0;
	bool	verbose = false;
	std::string	url_file;

    try {

        po::options_description desc("Allowed options");
        desc.add_options()
            ("help,h", "produce help message")
            ("verbose,v", "output verbose message")

			//("times", po::value<int>(&times)->default_value(DEFAULT_TIMES), "number of test to execute")
			//("max-cid", po::value<int>(&max_cid)->default_value(1), "maximum cid")
			//("data-size", po::value<int>(&data_size)->default_value( 32 ), "size of data chunk")

            ("ini-file", po::value<std::string>(&ini_file)->default_value("urlcab.ini"), "INI configure file")
            ("ini-section", po::value<std::string>(&ini_section)->default_value("sakcab::cad"), "INI configure section")

            ("url-file", po::value<std::string>(&url_file), "url list file")
        ;   

        po::variables_map vm;    
        po::store(po::parse_command_line(argc, argv, desc), vm);
        po::notify(vm);    

        if (vm.count("help")) {
            std::cout << desc << "\n";
            return 1;
        }   

		if (vm.count("verbose")) {
			verbose = true;
		}

		if (vm.count("url-file") < 1) {
			std::cout << " No url-file to load\n\n";
            std::cout << desc << "\n";
            return 1;
		}
    }   
    catch(std::exception& e) {
        std::cerr << "error: " << e.what() << "\n";
        return 1;
    }   
    catch(...) {
        std::cerr << "Exception of unknown type!\n";
		return 2;
    }   

	
    logger_p = bocat::common::getLogger();

	test_001( ini_file, ini_section, url_file );


	return	0;
}

